from PySide6.QtWidgets import *
from PySide6.QtGui import *
from PySide6.QtCore import QIODevice
from PySide6.QtSerialPort import QSerialPort,QSerialPortInfo
import numpy as np
import cv2
import os

from ui.MainWindow import Ui_MainWindow

import Fun as F
import Var as V

class Uipy_MainWindow(QMainWindow,Ui_MainWindow):
    def __init__(self,parent=None):
        super(Uipy_MainWindow, self).__init__(parent)
        self.setupUi(self)

        self.nowFileType = '图片'                 # 值:图片/视频
        self.setControlVisibleByFileType()       # 调用：根据文件类型设置控件显隐
        self.setVisibleByThreshold()             # 根据阈值分割算法设置控件可见
        # 设置Tooltips
        self.initTooltips()

        # 扫描串口列表
        self.scanPortList()

        # 波特率列表
        self.cB_baud.addItems([str(i) for i in QSerialPortInfo.standardBaudRates()])
        self.cB_baud.setCurrentText(str(V.default['baud']))

        self.pB_LoadImageOrVideo.clicked.connect(self.selectFile)       # 加载文件
        self.pB_portScan.clicked.connect(self.scanPortList)             # 扫描串口
        self.pB_playerPlay.clicked.connect(self.playerPlay)             # 播放视频
        self.pB_Transfer.clicked.connect(self.Transer)                  # 开始传输
        self.pB_stopPlayer.clicked.connect(self.stopPlay)               # 停止播放
        self.pB_stopTranser.clicked.connect(self.stopTranser)           # 停止传输
        self.pB_transeraframe.clicked.connect(self.transerFrame)        # 传输单帧空满
        self.pB_JumpFirstFrame.clicked.connect(self.jumpFirstFrame)     # 跳转首帧
        # 链接阈值分割算法改变事件与相关控件显隐
        self.cB_threshold.currentTextChanged.connect(self.setVisibleByThreshold)
        # 链接控件值与变量
        self.sB_Width_Transer.valueChanged.connect(self.setControlValueToNum)
        self.sB_Height_Transer.valueChanged.connect(self.setControlValueToNum)
        self.sB_Width_Image.valueChanged.connect(self.setControlValueToNum)
        self.sB_Height_Image.valueChanged.connect(self.setControlValueToNum)
        self.sB_fps.valueChanged.connect(self.setControlValueToNum)
        self.sB_fs.valueChanged.connect(self.setControlValueToNum)
        self.sB_thValue.valueChanged.connect(self.setControlValueToNum)
        self.sB_area.valueChanged.connect(self.setControlValueToNum)
        self.sB_sub_area.valueChanged.connect(self.setControlValueToNum)

        self.cap = cv2.VideoCapture()           # 视频对象
        self.img = None                         # 图片对象
        self.com = QSerialPort()  # 串口对象
        self.isTranser = False                  # 是否在传输，为false时候停止视频读取传输
        self.isPlayer = False                   # 是否播放，为false时候停止读取

        self.fileInfo = {
            'path':'',                    # 路径
            'pos-mesc':0,                # 当前位置，ms单位
            'pos-frames':0,              # 当前帧
            'fps':30,                     # 帧率
            'frame-count':0,             # 帧数
            'width':0,                   # 宽度
            'height':0,                  # 长度
        }
        self.setControlDefaultValue()           # 设置控件的默认值
        self.Num = {
            'fps':self.sB_fps.value(),                          # 帧率
            'fs':self.sB_fs.value(),                            # 跳帧
            'tran-width':self.sB_Width_Transer.value(),         # 传输宽
            'tran-height':self.sB_Height_Transer.value(),       # 传输高
            'video-width':V.default['video']['width'],          # 视频宽
            'video-height':V.default['video']['height'],        # 视频高
            'image-width':self.sB_Width_Image.value(),          # 图片宽
            'image-height':self.sB_Height_Image.value(),        # 图片高

            'th':self.sB_thValue.value(),                       # 固定阈值大小
            'side-area':self.sB_area.value(),                   # 小区域阈值面积
            'sub-area':self.sB_sub_area.value()                 # 小区域阈值减值
        }



    def setControlVisibleByFileType(self):
        '''
        根据文件类型设置控件显隐
        '''
        if self.nowFileType == '视频':
            self.groupVideo_Jump.setVisible(True)
            self.videoFrame.setVisible(True)
            self.groupProcess.setGeometry(self.groupProcess.x(), self.groupProcess.y(), self.groupProcess.width(), 176)
            self.groupAlgorithm.setGeometry(self.groupAlgorithm.x(), 335, self.groupAlgorithm.width(),
                                            self.groupAlgorithm.height())
            self.groupPreview.setVisible(True)
            self.groupPort.setGeometry(0,70,self.groupPort.width(),self.groupPort.height())
            self.groupTranser.setGeometry(0,200,self.groupTranser.width(),self.groupTranser.height())
        elif self.nowFileType == '图片':
            self.groupVideo_Jump.setVisible(False)
            self.videoFrame.setVisible(False)
            self.groupProcess.setGeometry(self.groupProcess.x(),self.groupProcess.y(),self.groupProcess.width(),116)
            self.groupAlgorithm.setGeometry(self.groupAlgorithm.x(),275,self.groupAlgorithm.width(),self.groupAlgorithm.height())
            self.groupPreview.setVisible(False)
            self.groupPort.setGeometry(0,5,self.groupPort.width(),self.groupPort.height())
            self.groupTranser.setGeometry(0,135,self.groupTranser.width(),self.groupTranser.height())

    def setVisibleByThreshold(self):
        '''
        根据阈值分割算法设置控件显隐
        '''
        cth = self.cB_threshold.currentText()
        self.lbl_th.setVisible(False)
        self.lbl_th_2.setVisible(False)
        self.lbl_th_3.setVisible(False)
        self.sB_thValue.setVisible(False)
        self.sB_area.setVisible(False)
        self.sB_sub_area.setVisible(False)
        if cth == '固定阈值':
            self.lbl_th.setVisible(True)
            self.sB_thValue.setVisible(True)
        elif cth in ['小区域均值','小区域加权高斯核']:
            self.lbl_th_2.setVisible(True)
            self.lbl_th_3.setVisible(True)
            self.sB_area.setVisible(True)
            self.sB_sub_area.setVisible(True)

    def setControlDefaultValue(self):
        '''
        设置控件默认值
        '''
        self.sB_Width_Transer.setValue(V.default['tran-width'])
        self.sB_Height_Transer.setValue(V.default['tran-height'])
        self.sB_Width_Image.setValue(V.default['image']['width'])
        self.sB_Height_Image.setValue(V.default['image']['height'])
        self.sB_fps.setValue(V.default['video']['fps'])
        self.sB_fs.setValue(V.default['video']['fs'])
        self.sB_thValue.setValue(V.default['th'])
        self.sB_area.setValue(V.default['side-area'])
        self.sB_sub_area.setValue(V.default['sub-area'])

    def setControlValueToNum(self):
        '''
        设置控件值给变量
        '''
        if self.sB_Width_Image.value() > self.sB_Width_Transer.value():
            self.sB_Width_Transer.setValue(self.sB_Width_Image.value())
        if self.sB_Height_Image.value() > self.sB_Height_Transer.value():
            self.sB_Height_Transer.setValue(self.sB_Height_Image.value())

        self.Num = {
            'fps':self.sB_fps.value(),
            'fs':self.sB_fs.value(),
            'tran-width':self.sB_Width_Transer.value(),
            'tran-height':self.sB_Height_Transer.value(),
            'video-width':self.sB_Width_Image.value(),
            'video-height':self.sB_Height_Image.value(),
            'th':self.sB_thValue.value(),
            'side-area':self.sB_area.value(),
            'sub-area':self.sB_sub_area.value()
        }
        if self.nowFileType == '图片':
            self.Num['image-width'] = self.sB_Width_Image.width()
            self.Num['image-height'] = self.sB_Height_Image.height()

            if len(self.fileInfo['path']):
                self.isPlayer = True
                src = cv2.imdecode(np.fromfile(self.fileInfo['path'], dtype=np.uint8), -1)
                self.ProcessImg(src)
                self.isPlayer = False

    def initTooltips(self):
        self.pB_LoadImageOrVideo.setToolTip(u'选择并加载一个文件')
        self.sB_fps.setToolTip(u'设置视频的帧率')
        self.sB_fs.setToolTip(u'取一帧之后跳过多少帧再取')
        self.sB_Width_Transer.setToolTip(u'设置目标屏幕的宽')
        self.sB_Height_Transer.setToolTip(u'设置为目标屏幕的高')
        self.sB_Width_Image.setToolTip(u'修改视频的宽')
        self.sB_Height_Image.setToolTip(u'修改视频的高')
        self.cB_fillwhite.setToolTip(u'对视频不足屏幕大小的部分填充满(黑白屏幕即为白)')
        self.cB_invertColor.setToolTip(u'反转颜色')
        self.cB_threshold.setToolTip(u'选择阈值分割算法')
        self.pB_playerPlay.setToolTip(u'播放视频')
        self.pB_stopPlayer.setToolTip(u'停止播放视频')
        self.pB_Transfer.setToolTip(u'开始传输')
        self.pB_stopTranser.setToolTip(u'停止传输')
        self.pB_portScan.setToolTip(u'扫描串口')
        self.pB_openPort.setToolTip(u'打开串口')
        self.pB_closePort.setToolTip(u'关闭串口')
        self.pB_transeraframe.setToolTip(u'传输单帧全空/满')
        self.pB_JumpFirstFrame.setToolTip(u'跳转至首帧')

    def setInfo(self):
        self.lbl_info.setText(f"当前帧：{self.fileInfo['pos-frames']}\n当前位置(ms):{'%.3f'%self.fileInfo['pos-mesc']}")

    def selectFile(self):
        '''
        读取一个文件
        :return:
        '''
        fileName,fileType = QFileDialog.getOpenFileName(self,"选择文件",'','所有文件(*)')
        if fileName:
            self.lE_filePath.setText(fileName)
            # 此处自行填充为opencv支持的文件类型扩展名
            if os.path.splitext(fileName)[-1] in V.supportExtension_Video:
                self.nowFileType = '视频'
            elif os.path.splitext(fileName)[-1] in V.supportExtension_Image:
                self.nowFileType = '图片'

            self.setControlVisibleByFileType()

            if self.nowFileType == '视频':
                self.cap = cv2.VideoCapture(fileName)
                self.fileInfo['path'] = fileName
                # 要获取的信息
                lst = ['pos-mesc','pos-frames','fps','frame-count','width','height']
                # 要获取信息在cap.get()函数位置列表
                dlst = [0,1,5,7,3,4]
                for i in range(len(lst)):
                    self.fileInfo[lst[i]] = self.cap.get(dlst[i])

            elif self.nowFileType == '图片':
                self.fileInfo['path'] = fileName
                # src = cv2.imread(fileName)
                # 当遇到中文路径读取不到，始终返回None
                src = cv2.imdecode(np.fromfile(fileName, dtype=np.uint8), -1)
                sp = src.shape
                self.Num['image-width'] = sp[1]
                self.Num['image-height'] = sp[0]
                self.com.waitForBytesWritten(-1)
                self.isPlayer = True
                self.ProcessImg(src)
                self.isPlayer = False
            else:
                F.msgbox('提示','不支持该类型!')
                return

    def setSerialParamsByControl(self):
        '''
        根据控件参数设置Serial
        '''
        try:
            self.com.setPortName(self.cB_port.currentText())
            self.com.setBaudRate(int(self.cB_baud.currentText()))
            self.com.setDataBits(QSerialPort.Data8)
            self.com.setParity(QSerialPort.NoParity)
            self.com.setStopBits(QSerialPort.OneStop)
            self.com.setFlowControl(QSerialPort.NoFlowControl)
            self.com.waitForBytesWritten(-1)

        except Exception as e:
            F.msgbox('提示',e)

    def transerFrame(self):
        '''
        传输一帧全空或者全满
        :return:
        '''
        try:
            self.setSerialParamsByControl()
            if not self.com.isOpen():
                if not self.com.open(QIODevice.WriteOnly):
                    return
            inserNum = 0 if self.cB_transeraframe_color.currentText() == '全黑' else 255

            transerImg = np.full((128,64),inserNum)

            self.sendImg(transerImg)
        except Exception as e:
            F.msgbox('错误',e)

    def Transer(self):
        self.setSerialParamsByControl()
        if not self.com.isOpen():
            if not self.com.open(QIODevice.WriteOnly):
                return
        self.isTranser = True
        if self.nowFileType == '图片':
            pass
        elif self.nowFileType == '视频':
            if not self.isPlayer:
                self.readVideoFile()

    def ProcessImg(self,img):
        '''
        处理图片
        '''
        # 修改大小
        img = cv2.resize(img,
                         (self.Num['video-width'], self.Num['video-height']),
                         interpolation=cv2.INTER_CUBIC)
        # 灰度
        img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        # 二值化
        cth = self.cB_threshold.currentText()
        if cth == 'Otsu':
            ret1, img = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
        elif cth == '固定阈值':
            ret, img = cv2.threshold(img, self.Num['th'], 255, cv2.THRESH_BINARY)
        elif cth == '小区域均值':
            img = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_MEAN_C,
                                        cv2.THRESH_BINARY, self.Num['side-area'], self.Num['sub-area'])
        elif cth == '小区域加权高斯核':
            img = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
                                        cv2.THRESH_BINARY, self.Num['side-area'], self.Num['sub-area'])
        # 填充
        if self.Num['video-height'] < self.Num['tran-height']:
            tnum = int((self.Num['tran-height'] - self.Num['video-height']) / 2)
            bnum = int(self.Num['tran-height'] - tnum - self.Num['video-height'])
            insertNum = 255 if self.cB_fillwhite.isChecked() else 0

            for i in range(bnum):
                img = np.insert(img, self.Num['video-height'], insertNum, axis=0)

            for i in range(tnum):
                img = np.insert(img, 0, insertNum, axis=0)

        if self.Num['video-width'] < self.Num['tran-width']:
            lnum = int((self.Num['tran-width'] - self.Num['video-width']) / 2)
            rnum = int(self.Num['tran-width'] - lnum - self.Num['video-width'])

            insertNum = 255 if self.cB_fillwhite.isChecked() else 0

            for i in range(rnum):
                img = np.insert(img, self.Num['video-width'], insertNum, axis=1)
            for i in range(lnum):
                img = np.insert(img, 0, insertNum, axis=1)

        # 反转颜色
        if self.cB_invertColor.isChecked():
            for i in range(img.shape[0]):
                for j in range(img.shape[1]):
                    img[i][j] = 0 if img[i][j] == 255 else 255

        if self.isPlayer:
            # 转换成为可显示在控件上的格式
            view_img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)
            self.view_img = QImage(view_img.data,
                              view_img.shape[1],
                              view_img.shape[0],
                              QImage.Format_RGB888)
            # 设置并且显示图像
            self.lbl_Player.setPixmap(QPixmap(self.view_img))
            self.lbl_Player.show()

        if self.isTranser:
            self.com.waitForBytesWritten(-1)
            self.sendImg(img)

    def readVideoFile(self):
        if self.fileInfo['pos-frames'] == self.fileInfo['frame-count']:
            self.cap.release()
            self.cap = cv2.VideoCapture(self.fileInfo['path'])

        while self.isPlayer or self.isTranser:
            # 跳帧
            if self.Num['fs'] != 0:
                for i in range(self.Num['fs']):
                    ret, frame = self.cap.read()
            ret, frame = self.cap.read()
            if ret:
                self.fileInfo['pos-mesc'] = self.cap.get(0)
                self.fileInfo['pos-frames'] = self.cap.get(1)
                self.setInfo()

                self.com.waitForBytesWritten(-1)
                self.ProcessImg(frame)
                cv2.waitKey(int(1000 / self.Num['fps']))
            else:
                self.isPlayer = False

    def playerPlay(self):
        if not len(self.fileInfo['path']):
            F.msgbox('提示','请选择一个文件')
            return

        self.isPlayer = True
        if not self.isTranser:
            self.readVideoFile()

    def scanPortList(self):
        self.cB_port.clear()
        for sP in QSerialPortInfo.availablePorts():
            self.cB_port.addItem(sP.portName())

    def openPort(self):
        try:
            self.setSerialParamsByControl()
            self.com.open(QIODevice.WriteOnly)
            return True
        except Exception as e:
            F.msgbox('串口错误', f'{e}')
            return False

    def closePort(self):
        try:
            self.com.close()
        except Exception as e:
            F.msgbox('提示',e)

    def sendImg(self,img):
        binStrlst = []
        idx = 8
        s = ''
        for i in img:
            for j in i:
                s += '1' if j == 255 else '0'
                idx -= 1
                if idx == 0:
                    binStrlst.append(s)
                    s = ''
                    idx = 8
        for i in binStrlst:
            j = int(i, 2)
            j = hex(j)
            if len(j) == 3:
                j = j[:-1] + '0' + j[-1]
            hex_str = bytes.fromhex(j[2:])
            self.com.write(hex_str)

    def jumpFirstFrame(self):
        '''
        跳转首帧
        '''
        if len(self.fileInfo['path']):
            self.cap.release()
            self.cap = cv2.VideoCapture(self.fileInfo['path'])
            ret,frame = self.cap.read()

            self.fileInfo['pos-mesc'] = self.cap.get(0)
            self.fileInfo['pos-frames'] = self.cap.get(1)

        else:
            F.msgbox('提示','请选择一个视频！')


    def stopPlay(self):
        self.isPlayer = False

    def stopTranser(self):
        self.isTranser = False

    def closeEvent(self, *args, **kwargs):
        try:
            # 释放对象
            self.cap.release()
            self.com.close()
        except Exception as e:
            F.msgbox('提示',f'错误：{str(e)}')